﻿//------------------------------------------------------------------------------
// <copyright file="PepperService.svc.cs" company="Robert MacLean">
//      Copyright © Robert MacLean.  All Rights Reserved.
// </copyright>
//
//  Details on this see: http://pepper.codeplex.com
//  This code released under the terms of the 
//  Microsoft Public License  (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//------------------------------------------------------------------------------

namespace Pepper
{
    using System;
    using System.Linq;
    using System.ServiceModel;
    using Pepper.Exceptions;
    using System.Configuration;

    public class PepperService2 : IPepperService2
    {
        private static void RaiseCredentialsFault(string message, string id)
        {
            var fault = new InvalidCredentialsFault()
                            {
                                Message = message,
                                FaultId = id
                            };
            var faultReason = new FaultReason(message);

            throw new FaultException<InvalidCredentialsFault>(fault, faultReason);
        }

        private static User GetUser(PepperDataContext db, string email)
        {
            var user = (from u in db.Users
                        where u.Email_Address == email
                        select u).SingleOrDefault();

            return user;
        }

        public Guid Login(string emailAddress, string passwordHash)
        {
            var normalisedEmail = emailAddress.ToUpperInvariant();
            using (var db = GetPepperEntities())
            {
                var user = GetUser(db, normalisedEmail);

                if (user != null)
                {
                    if (user.Password.Equals(passwordHash))
                    {
                        return user.PK;
                    }
                    else
                    {
                        RaiseCredentialsFault("Invalid password", "0x10000000");
                        return Guid.Empty;
                    }
                }
                else
                {
                    var id = Guid.NewGuid();
                    var newUser = new User()
                    {
                        PK = id,
                        Email_Address = normalisedEmail,
                        Password = passwordHash
                    };

                    db.Users.InsertOnSubmit(newUser);
                    db.SubmitChanges();
                    return id;
                }
            }
        }

        private static PepperDataContext GetPepperEntities(bool @readonly = false)
        {
            var db = new PepperDataContext(ConfigurationManager.ConnectionStrings["PepperConnectionString"].ConnectionString) { CommandTimeout = 300 };
            if (@readonly)
            {
                db.ObjectTrackingEnabled = false;
            }

            return db;
        }

        private static User GetUser(PepperDataContext context, Guid userId)
        {
            var user = (from u in context.Users
                        where u.PK == userId
                        select u).SingleOrDefault();

            if (user != null)
            {
                return user;
            }
            else
            {
                RaiseCredentialsFault("Invalid user id", "0x10000002");
                return null;
            }
        }

        public Tuple<byte[], string> LoadSettings(Guid userId, string passwordHash)
        {
            using (var db = GetPepperEntities(true))
            {
                var user = GetUser(db, userId);

                if (user.Password.Equals(passwordHash))
                {
                    var setting = (from s in db.Settings
                                   where s.User == user.PK
                                   select s).SingleOrDefault();

                    if (setting != null)
                    {
                        return Tuple.Create(setting.Settings.ToArray(), setting.Hash);
                    }
                    else
                    {
                        throw new FaultException<NoSettingsFault>(new NoSettingsFault(), new FaultReason("No Settings Found"));
                    }
                }
                else
                {
                    RaiseCredentialsFault("Invalid password", "0x10000001");
                    return null;
                }
            }
        }

        public Tuple<byte[], string> LoadPublicSettings(Guid publicSettingId)
        {
            using (var db = GetPepperEntities())
            {
                var setting = GetSettings(db, publicSettingId);

                if (setting != null)
                {
                    if (!setting.Public.HasValue || !setting.Public.Value)
                    {
                        throw new FaultException<NotPublicSettingsFault>(new NotPublicSettingsFault(), new FaultReason("Settings are not public"));
                    }

                    return Tuple.Create(setting.Settings.ToArray(), setting.Hash);
                }
                else
                {
                    throw new FaultException<NoSettingsFault>(new NoSettingsFault(), new FaultReason("No Settings Found"));
                }
            }
        }

        public void SaveSettings(Guid userId, string passwordHash, byte[] settings, string settingsHash, bool publicSettings)
        {
            using (var db = GetPepperEntities())
            {
                var existingSetting = GetSettings(db, userId, passwordHash);
                if (existingSetting != null)
                {
                    existingSetting.Settings = settings;
                    existingSetting.Hash = settingsHash;
                    db.SubmitChanges();
                }
                else
                {
                    var newSetting = new Setting()
                    {
                        PK = Guid.NewGuid(),
                        Hash = settingsHash,
                        Settings = settings,
                        User = userId,
                        Public = publicSettings
                    };

                    db.Settings.InsertOnSubmit(newSetting);
                    db.SubmitChanges();
                }
            }
        }

        private static Setting GetSettings(PepperDataContext db, Guid userId, string passwordHash = null)
        {
            var user = GetUser(db, userId);

            if (passwordHash != null)
            {
                if (!user.Password.Equals(passwordHash))
                {
                    RaiseCredentialsFault("Invalid password", "0x10000004");
                    return null;
                }
            }

            var setting = (from s in db.Settings
                           where s.User == user.PK
                           select s).SingleOrDefault();

            return setting;
        }

        public bool SettingsExist(Guid userId, string passwordHash)
        {
            using (var db = GetPepperEntities(true))
            {
                return GetSettings(db, userId, passwordHash) != null;
            }
        }

        public bool AreSettingsPublic(Guid publicSettingId)
        {
            using (var db = GetPepperEntities(true))
            {
                var setting = GetSettings(db, publicSettingId);
                if (setting != null)
                {
                    return setting.Public.HasValue ? setting.Public.Value : false;
                }
                else
                {
                    throw new FaultException<NoSettingsFault>(new NoSettingsFault(), new FaultReason("No Settings Found"));
                }
            }
        }

        public void ClearSetting(Guid userId, string passwordHash)
        {
            using (var db = GetPepperEntities(true))
            {
                var setting = GetSettings(db, userId, passwordHash);
                if (setting != null)
                {
                    db.Settings.DeleteOnSubmit(setting);
                    db.SubmitChanges();
                }
                else
                {
                    throw new FaultException<NoSettingsFault>(new NoSettingsFault(), new FaultReason("No Settings Found"));
                }
            }
        }

        public int SettingsCount()
        {
            using (var db = GetPepperEntities(true))
            {
                return db.Settings.Count();
            }
        }
    }
}
